#include "pyvim.h"


PyDoc_STRVAR(screen_buffer_doc,
"A screen buffer for a vim window\n");

static PyObject *
screen_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	return type->tp_alloc(type, 0);
}

static int
screen_buffer_init(PvScreenBuffer *self, PyObject *args, PyObject *kwds)
{
	int rows = 0;
	int cols = 0;

	if (!PyArg_ParseTuple(args, "ii", &rows, &cols))
		return -1;

	self->buf = PyMem_Malloc(rows * cols * sizeof(Py_UNICODE));
	Py_UNICODE_FILL(self->buf, ' ', rows * cols);
	//memset(self->buf, 0, rows * cols * sizeof(Py_UNICODE));
	if (self->buf == NULL) {
		PyErr_SetString(PyExc_RuntimeError, 
				"Can't alloc memory to sceen buffer.\n");
		return -1;
	}
	self->rows = rows;
	self->cols = cols;
	return 0;
}

static void
screen_buffer_dealloc(PyObject *me)
{
	PvScreenBuffer *self = (PvScreenBuffer *)me;
	if (self->buf != NULL)
		PyMem_Free(self->buf);
}


PyObject *
PvScreenBuffer_Putstr(PyObject *me, int row, int col, 
		Py_UNICODE *str, int length)
{
	PvScreenBuffer *self = (PvScreenBuffer *)me;
	int i, line_off;
	int isend = 0;
	Py_UNICODE c;

	line_off = row * self->cols;
	for (i = 0; i < length && col < self->cols; i++, col++) {
		c = str[i];
		self->buf[line_off+col] = c;
	}

	Py_RETURN_NONE;
}

PyDoc_STRVAR(screen_buffer_putstr_doc,
"insert a string to screen buffer at row, col\n"
"putstr(row, col, str, length)\n");

static PyObject *
screen_buffer_putstr(PyObject *self, PyObject *args)
{
	PyUnicodeObject	*u;
	int			row;
	int			col;
	int			length = -1;

	if (!PyArg_ParseTuple(args, "iiU|i", &row, &col, &u, &length))
		return NULL;

	if (length == -1)
		length = u->length;

	length = MIN(u->length, length);

	if (!PvScreenBuffer_Putstr(self, row, col, u->str, length))
		return NULL;
	
	Py_RETURN_NONE;
}

/* return a NULL terminal unicode string (new alloc) must 
 * control it lifetime yourself
 * */
Py_UNICODE *
PvScreenBuffer_Getstr(PyObject *me, int row, int col, int length)
{
	PvScreenBuffer *self = (PvScreenBuffer *)me;
	Py_UNICODE	*str;
	int i, w;
	Py_UNICODE	c;
	int line_off = row * self->cols;

	length = MIN(length, self->cols-col);
	str = PyMem_Malloc((length + 1) * sizeof(Py_UNICODE));
	//Py_UNICODE_FILL(str, ' ', length);

	for (i = 0; i < length && col < self->cols; i++) {
		c = self->buf[line_off + col];
		w = wcwidth(c);
		//printf("row, i, col, cells, char:%d, %d, %d, %d, %x\n", row, i, col, w, c);
		if (c == 0) {
			PyErr_SetString(PyExc_ValueError,
					"How can you get the zero value");
			return NULL;
			//break;
		}
		if (w == 1)
			str[i] = c;
		else if (w == 2) {
			str[i] = c;
			col += 2;
			continue;
		} else if (w == -1) {
			printf("row %d, col %d, char %x\n", row, col, c);
			PyErr_SetString(PyExc_ValueError,
					"How can you get the invalid char value");
			return NULL;
		}
		col++;
	}
	str[length] = 0;

	return str;
}

PyDoc_STRVAR(screen_buffer_getstr_doc,
"get a string from screen buffer at row, col\n"
"getstr(row, col, length)\n");

static PyObject *
screen_buffer_getstr(PyObject *self, PyObject *args)
{
	int	row, col, length;
	Py_UNICODE	*str;
	PyObject	*ret;

	if (!PyArg_ParseTuple(args, "iii", &row, &col, &length))
		return NULL;

	str = PvScreenBuffer_Getstr(self, row, col, length);
	if (str == NULL)
		return NULL;

	ret = PyUnicode_FromWideChar(str, -1);
	if (ret == NULL)
		return NULL;
	PyMem_Free(str);
	return ret;
}

PyObject *
PvScreenBuffer_Clear(PvScreenBuffer *self, 
		int row1, int col1, int row2, int col2)
{
	int row, len, line_off;

	col2 = MIN(self->cols - 1, col2);
	if (col1 > col2 || row1 >= self->rows) 
		Py_RETURN_NONE;

	len = col2 - col1 + 1;

	for (row = row1; row <= row2 && row < self->rows; row++) {
		line_off = row * self->cols;
		memset(self->buf + line_off + col1, 0, len * sizeof(Py_UNICODE));
	}
	Py_RETURN_NONE;
}

#if 0
PyObject *
PvScreenBuffer_Next(PvScreenBuffer *self, int *row, int *col)
{
	int off, cells;
	Py_UNICODE c, c1;

	if (*col >= self->cols || *row >= self->rows) {
		PyErr_SetString(PyExc_ValueError,
				"the pos(row, cols) not in screen");
		return NULL;
	}
	if (*col == self->cols - 1 || *row == self->rows - 1) {
		PyErr_SetString(PyExc_ValueError,
				"the scroll function not implement");
		return NULL;
	}

	off = (*row) * self->cols;
	c = self->buf[off+(*col)];

	if (c == 0) {
		PyErr_SetString(PyExc_ValueError,
				"PvScreenBuffer_Next can't handle the cell who's value is 0");
		return NULL;
	}

	cells = wcwidth(c);
	if (cells == 1) {
		c1 = self->buf[off+(*col)+1];
		if (c1 == 0 || c1 == '\n') {
			Py_RETURN_NONE;
		} else {
			(*col)++;
		}
	}
	else if (cells == 2) {
		if (*col == self->cols - 2 ) {
			PyErr_SetString(PyExc_ValueError,
					"the scroll function not implement");
			return NULL;
		}
		(*col) += 2;
	} else if (cells == -1) {
		if (c == '\t') {
			int t = COMPUTE_TAB((*col), self->tabstop);
			c1 = self->buf[off+(*col)+t];
			if (t + (*col) >= self->cols) {
				PyErr_SetString(PyExc_ValueError,
						"the scroll function not implement");
				return NULL;
			}
			if (c1 == 0 || c1 == '\n')
				Py_RETURN_NONE;
			(*col) += t;
		}
	}
	Py_RETURN_NONE;
}

PyDoc_STRVAR(screen_buffer_next_doc,
"get the next pos in screen for a specified pos\n"
"next(row, col)\n");

static PyObject *
screen_buffer_next(PvScreenBuffer *self, PyObject *args)
{
	int	row, col;

	if (!PyArg_ParseTuple(args, "ii", &row, &col))
		return NULL;

	PvScreenBuffer_Next(self, &row, &col);

	return Py_BuildValue("ii", row, col);
}
#endif

static PyMemberDef screen_buffer_members[] = {
	{"rows", T_INT, offsetof(PvScreenBuffer, rows), 0, 
		"the number of max rows show in the screen"},
	{"cols", T_INT, offsetof(PvScreenBuffer, cols), 0, 
		"the number of max cols show in the screen"},
	{NULL}
};

static PyMethodDef screen_buffer_methods[] = {
	{"putstr", (PyCFunction)screen_buffer_putstr, METH_VARARGS,
		screen_buffer_putstr_doc},
	{"getstr", (PyCFunction)screen_buffer_getstr, METH_VARARGS,
		screen_buffer_getstr_doc},
	{NULL, NULL}
};

PyTypeObject PvScreenBuffer_Type = {
	PyObject_HEAD_INIT(0)
        "pyvim.screen_buffer",		/* tp_name */
        sizeof(PvScreenBuffer),		/* tp_basicsize */
        0,				/* tp_itemsize */
        screen_buffer_dealloc,		/* tp_dealloc */
        0,				/* tp_print */
        0,				/* tp_getattr */
        0,				/* tp_setattr */
        0,				/* tp_reserved */
        0,				/* tp_repr */
        0,				/* tp_as_number */
        0,				/* tp_as_sequence */
        0,				/* tp_as_mapping */
        0,				/* tp_hash */
        0,				/* tp_call */
        0,				/* tp_str */
        PyObject_GenericGetAttr,	/* tp_getattro */
        0,				/* tp_setattro */
        0,				/* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,		/* tp_flags */
        screen_buffer_doc,		/* tp_doc */
        0,				/* tp_traverse */
        0,				/* tp_clear */
        0,				/* tp_richcompare */
        0,				/* tp_weaklistoffset */
        0,				/* tp_iter */
        0,				/* tp_iternext */
        screen_buffer_methods,		/* tp_methods */
        screen_buffer_members,				/* tp_members */
        0,				/* tp_getset */
        0,				/* tp_base */
        0,				/* tp_dict */
        0,				/* tp_descr_get */
        0,				/* tp_descr_set */
        0,				/* tp_dictoffset */
        (initproc)screen_buffer_init,				/* tp_init */
        0,				/* tp_alloc */
        screen_buffer_new,				/* tp_new */
        0,				/* tp_free */
        0,				/* tp_is_gc */
};

PyDoc_STRVAR(screen_doc,
"A screen for a vim window\n");

static PyObject *
screen_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	return type->tp_alloc(type, 0);
}

static int
screen_init(PvScreen *self, PyObject *args, PyObject *kwds)
{
	if (!PyArg_ParseTuple(args, "ii", &self->rows, &self->cols))
		return -1;

	self->cmd_lines = 1;

	self->topframe = PyObject_CallFunction((PyObject *)&PvFrame_Type,
			"ii", self->rows - self->cmd_lines, self->cols);
	if (self->topframe == NULL) 
		return -1;
	self->curframe = self->topframe;

	FR_WIN(self->topframe) = PyObject_CallFunction((PyObject *)&PvWindow_Type,
			"O", self->topframe);

	self->screen_buf = PyObject_CallFunction((PyObject *)&PvScreenBuffer_Type,
			"ii", self->rows, self->cols);
	if (self->screen_buf == NULL)
		return -1;

	return 0;
}

static void
screen_dealloc(PyObject *self)
{
	Py_XDECREF(SCR_TOPFR(self));
	Py_XDECREF(SCR_BUF(self));
}

PyObject *
PvScreen_Update(PyObject *self, int row1, int col1, int row2, int col2)
{
	int top, left, bottom, right;
	int i, row;
	long hash;
	PyObject *win;
	PyObject *line;

	/* traverse all windows for update */
	i = 0;
	while (_PySet_NextEntry(WINDOW_SET, &i, &win, &hash)) {
		/* get the intersection area */
		top = start_intersection(row1, row2, WIN_TOP(win), WIN_BOTTOM(win));
		if (top == -1)
			continue;
		bottom = end_intersection(row1, row2, WIN_TOP(win), WIN_BOTTOM(win));
		if (bottom == -1)
			continue;
		left = start_intersection(col1, col2, WIN_LEFT(win), WIN_RIGHT(win));
		if (left == -1)
			continue;
		right = end_intersection(col1, col2, WIN_LEFT(win), WIN_RIGHT(win));
		if (right == -1)
			continue;

		PvWindow_Update(win, ROW_SCR2WIN(win, top), COL_SCR2WIN(win, left),
			   ROW_SCR2WIN(win, bottom), COL_SCR2WIN(win, right));
		for (row = top; row <= bottom; row++) {
			line = PvWindow_Gets(win, ROW_SCR2WIN(win, row), 
					COL_SCR2WIN(win, left), COL_SCR2WIN(win, right));
			assert(PyUnicode_GetSize(line) == right - left + 1);
			PvScreen_Puts(self, row, left, 
					PyUnicode_AS_UNICODE(line), right - left + 1);
		}
	}
	/* TODO: show the fake command line *not implement* */
	PvScreen_Puts(self, SCR_ROWS(self) - SCR_CMD_LINES(self), 0,
			L":command line", lstrlenW(L":command line"));
	Py_RETURN_NONE;
}

PyDoc_STRVAR(screen_update_doc,
"update(top, left, bottom, right)\n"
"update the specified area in screen\n"
);

static PyObject *
screen_update(PyObject *self, PyObject *args)
{
	int row1, col1, row2, col2;

	if (!PyArg_ParseTuple(args, "iiii", &row1, &col1, &row2, &col2))
		return NULL;

	return PvScreen_Update(self, row1, col1, row2, col2);
}

PyDoc_STRVAR(screen_gets_doc,
"get a substring of a given line\n"
"gets(row, col, length)\n"
"the row and col is the pos in screen\n"
);

static PyObject *
screen_gets(PyObject *self, PyObject *args)
{
	int	row, col, length;
	Py_UNICODE	*str;
	PyObject	*ret;

	if (!PyArg_ParseTuple(args, "iii", &row, &col, &length))
		return NULL;

	str = PvScreenBuffer_Getstr(SCR_BUF(self), row, col, length);
	if (str == NULL)
		return NULL;

	ret = PyUnicode_FromWideChar(str, -1);
	if (ret == NULL)
		return NULL;
	PyMem_Free(str);
	return ret;
}

PyDoc_STRVAR(screen_puts_doc,
"set a string to the screen at given pos\n"
"puts(row, col, length)\n"
"the row and col is the pos in screen\n"
);

static PyObject *
screen_puts(PyObject *self, PyObject *args)
{
	PyUnicodeObject	*u;
	int			row;
	int			col;
	int			length = -1;

	if (!PyArg_ParseTuple(args, "iiU|i", &row, &col, &u, &length))
		return NULL;

	if (length == -1)
		length = u->length;

	length = MIN(u->length, length);

	if (!PvScreenBuffer_Putstr(SCR_BUF(self), row, col, u->str, length))
		return NULL;
	
	Py_RETURN_NONE;
}

static PyMemberDef screen_members[] = {
	{"topframe", T_OBJECT, offsetof(PvScreen, topframe), 0, 
		"topframe of the screen"},
	{"curframe", T_OBJECT, offsetof(PvScreen, curframe), 0, 
		"current frame of the screen"},
	{"screen_buf", T_OBJECT, offsetof(PvScreen, screen_buf), 0, 
		"buffer cache of this screen"},
	{"cols", T_INT, offsetof(PvScreen, cols), 0, 
		"colunm number of the screen"},
	{"rows", T_INT, offsetof(PvScreen, rows), 0, 
		"rows number of the screen "},
	{"cmd_lines", T_INT, offsetof(PvScreen, cmd_lines), 0, 
		"the number of lines of the command line part"},
	{NULL}
};

static PyMethodDef screen_methods[] = {
	{"puts", (PyCFunction)screen_puts, METH_VARARGS,
		screen_puts_doc},
	{"gets", (PyCFunction)screen_gets, METH_VARARGS,
		screen_gets_doc},
	{"update", (PyCFunction)screen_update, METH_VARARGS,
		screen_update_doc},
	{NULL, NULL}
};

PyTypeObject PvScreen_Type = {
	PyObject_HEAD_INIT(0)
        "pyvim.screen",		/* tp_name */
        sizeof(PvScreen),		/* tp_basicsize */
        0,				/* tp_itemsize */
        screen_dealloc,		/* tp_dealloc */
        0,				/* tp_print */
        0,				/* tp_getattr */
        0,				/* tp_setattr */
        0,				/* tp_reserved */
        0,				/* tp_repr */
        0,				/* tp_as_number */
        0,				/* tp_as_sequence */
        0,				/* tp_as_mapping */
        0,				/* tp_hash */
        0,				/* tp_call */
        0,				/* tp_str */
        PyObject_GenericGetAttr,	/* tp_getattro */
        0,				/* tp_setattro */
        0,				/* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,		/* tp_flags */
        screen_doc,		/* tp_doc */
        0,				/* tp_traverse */
        0,				/* tp_clear */
        0,				/* tp_richcompare */
        0,				/* tp_weaklistoffset */
        0,				/* tp_iter */
        0,				/* tp_iternext */
        screen_methods,		/* tp_methods */
        screen_members,				/* tp_members */
        0,				/* tp_getset */
        0,				/* tp_base */
        0,				/* tp_dict */
        0,				/* tp_descr_get */
        0,				/* tp_descr_set */
        0,				/* tp_dictoffset */
        (initproc)screen_init,				/* tp_init */
        0,				/* tp_alloc */
        screen_new,				/* tp_new */
        0,				/* tp_free */
        0,				/* tp_is_gc */
};
